#include	"Mesh.h"
#include	<fstream>
#include	<windows.h>
#include	<gl/gl.h>

Mesh::Mesh()
	:	m_name("")
	,	m_material()
	,	m_triangle()
{
}

Mesh::Mesh(const std::string & file)
	:	m_name("")
	,	m_material()
	,	m_triangle()
{
	Load(file);
}

Mesh::~Mesh()
{
	Cleanup();
}

void Mesh::Cleanup()
{
	m_name.clear();
	m_material.clear();
	m_triangle.clear();
}

void Mesh::Load(const std::string & file)
{
	std::ifstream stream(file.c_str(), std::ios::in | std::ios::binary);
	if(!stream)
	{
		throw std::exception(std::string("Can not open file: " + file).c_str());
	}
	ReadHeader(stream);
	ReadMaterials(stream);
	ReadTriangles(stream);
	stream.close();
}

void Mesh::Render() const
{
	glPushAttrib(GL_CURRENT_BIT  | GL_ENABLE_BIT);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_CULL_FACE);
		glEnable(GL_TEXTURE_2D);
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		unsigned int currentMaterial = unsigned int(-1);
		for(std::vector< Triangle >::const_iterator it = m_triangle.begin();
			it != m_triangle.end(); ++it)
		{
			if(currentMaterial != (*it).Material())
			{
				if(currentMaterial != unsigned int(-1))
				{
					glEnd();
				}
				currentMaterial = (*it).Material();
				m_material.at(currentMaterial).Set();
				glBegin(GL_TRIANGLES);
			}
			(*it).Render();
		}
		if(currentMaterial != unsigned int(-1))
		{
			glEnd();
		}
	glPopAttrib();
}

void Mesh::RenderNormals() const
{
	glPushAttrib(GL_CURRENT_BIT  | GL_ENABLE_BIT);
		glEnable(GL_DEPTH_TEST);
		if(m_triangle.size() > 0)
		{
			glLineWidth(2.0f);
			glBegin(GL_LINES);
				for(std::vector< Triangle >::const_iterator it = m_triangle.begin();
					it != m_triangle.end(); ++it)
				{
					(*it).RenderNormal();
				}
			glEnd();
		}
	glPopAttrib();
}

namespace
{

	void Read(std::istream & stream, unsigned int & value)
	{
		stream.read(reinterpret_cast< char * >(&value), sizeof(unsigned int));
		if(!stream)
		{
			throw std::exception("Read operation failed.");
		}
	}

	void Read(std::istream & stream, int & value)
	{
		stream.read(reinterpret_cast< char * >(&value), sizeof(int));
		if(!stream)
		{
			throw std::exception("Read operation failed.");
		}
	}

	void Read(std::istream & stream, float & value)
	{
		stream.read(reinterpret_cast< char * >(&value), sizeof(float));
		if(!stream)
		{
			throw std::exception("Read operation failed.");
		}
	}

	void Read(std::istream & stream, std::string & value)
	{
		unsigned int len = 0;
		Read(stream, len);
		char * buffer = new char[len + 1];
		std::memset(buffer, 0, len + 1);
		stream.read(buffer, sizeof(char) * len);
		if(!stream)
		{
			throw std::exception("Read operation failed.");
		}
		value = buffer;
	}

	void Read(std::istream & stream, Color & value)
	{
		for(unsigned int i = 0; i < 4; ++i)
		{
			Read(stream, value[i]);
		}
	}

	void Read(std::istream & stream, Vector2f & value)
	{
		for(unsigned int i = 0; i < 2; ++i)
		{
			Read(stream, value[i]);
		}
	}

	void Read(std::istream & stream, Vector3f & value)
	{
		for(unsigned int i = 0; i < 3; ++i)
		{
			Read(stream, value[i]);
		}
	}

}

void Mesh::ReadHeader(std::istream & stream)
{
	const unsigned int correctFileSignature = 0x43454D4F;
	const unsigned int correctFileVersion = 0x01000000;
	unsigned int fileSignature = 0;
	unsigned int fileVersion = 0;

	Read(stream, fileSignature);
	if(fileSignature != correctFileSignature)
	{
		throw std::exception("File is not a CEM (signature is missing).");
	}

	Read(stream, fileVersion);
	if(fileVersion != correctFileVersion)
	{
		throw std::exception("Wrong file version.");
	}

	unsigned int size = 0;

	Read(stream, size);
	m_material.resize(size);

	Read(stream, size);
	m_triangle.resize(size);
}

void Mesh::ReadMaterials(std::istream & stream)
{
	float shininess;
	Color diffuse, ambient, specular, emission;
	std::string textureName;
	for(unsigned int i = 0; i < m_material.size(); ++i)
	{
		Read(stream, shininess);
		Read(stream, diffuse);
		Read(stream, ambient);
		Read(stream, specular);
		Read(stream, emission);
		Read(stream, textureName);
		m_material.at(i).Initialize(shininess, diffuse, ambient, specular, emission, textureName);
	}
}

void Mesh::ReadTriangles(std::istream & stream)
{
	unsigned int material;
	Vector3f vertex[3];
	Vector2f texture[3];
	for(unsigned int i = 0; i < m_triangle.size(); ++i)
	{
		Read(stream, material);
		for(unsigned int j = 0; j < 3; ++j)
		{
			Read(stream, vertex[j]);
			Read(stream, texture[j]);
		}
		m_triangle.at(i).Set(material, vertex, texture);
	}
}

